這兩天被 Vue3 折磨得很慘,至今尚未完全脫離苦海,
但我們之所以選 Vue,絕不是為了吃苦來的。
Vue 有其過人之處,
其中之一就是所謂的 component 元件系統。
我們都知道,Vue 把【data 資料】與【view 顯示】分離開來,
讓我們可以聚焦於 data 資料的處理與操作,
然後讓 view 的外觀顯示,可根據 data 資料即時呈現。
在 Vue 實體的設定選項 options 物件中,
我們可以用 data 定義資料,
再透過 template 來定義所要呈現的 view 外觀。
放在 data 裡的原始資料,
可以透過 computed 衍生出其他資料,
也可以透過 methods 衍生出其他相關操作。
而在負責外觀顯示的 template 樣板裡,
我們可透過 v-html、v-text 或直接用 {{ }} 來綁定所要呈現的內容,
用 v-bind 綁定屬性、用 v-on 綁定事件、
更可以用 v-model 雙向綁定表單輸入,
然後再利用 v-if、v-for,讓 template 樣板可以像程式碼般靈活編寫。
由此可見,Vue 的 options 選項物件,
可透過 data 與 template 的定義,
把【資料】與【顯示】分離開來,同時又巧妙整合起來,
說它是 Vue 的精髓也不為過。
如果把這個 options 選項物件的概念進一步延伸,
就有了 component 元件的概念。
Vue 的 component 元件,主要也是由資料與樣板所組成。
它的結構很像 options 物件,自成一體。
其中多了一個 props,用來接受外來的資料,
進而控制內部的資料與顯示。
有了 component 元件之後,
template 樣板的編寫也會變得更加簡潔有力。
比如下面的 html 樣板:
<div>
<div id="component_1">
<div>
<div>
<div>
<div>
...
</div>
</div>
</div>
</div>
</div>
<div id="component_2">
<div>
<div>
...
</div>
</div>
</div>
</div>
如果把其中兩個有 id 的 div 區塊整合成 component 元件,
原本的 html 樣板就可以改寫成:
<div>
<component_1></component_1>
<component_2></component_2>
<div>
這樣不是簡潔多了嗎?
如果說 v-if、v-for 就像程式碼中常見的 if、for 指令,
component 則很類似程式碼中 function 或 object 的概念。
component 可以把一些經常重複出現的部分整合成獨立的元件,
供後續重複調用,這就很像 function 的概念;
而它把 data 資料與相關的操作方法整合起來,
又很像是 object 物件的概念。
今天我們就來練習一下,
如何把 options 選項物件轉化成 component 元件吧。
首先可以觀察一下目前的程式碼。
我們為了在句子面板中顯示原文,
於是在 panels.js 的 showInSentPanel() 裡,
建立了一個 Vue 實體:
vm = new Vue({...});
在這個 Vue 實體中,使用了一個落落長的 options 選項物件。
我們就來把這個 options 選項物件,
變成一個 component 元件吧。
首先建立一個 vue_components.js,
專門用來放隨後所建立的 Vue component 元件。
別忘了也要在 manifest.json 做好登記工作喲。
接著在 vue_components.js 中,
建立一個叫做 orig_sent 的元件:
// 原文句
Vue.component('orig_sent', { ... } );
只要把剛才那個落落長的 options 選項物件,放到後面的參數中,
就可以把它變成一個獨立的 component 元件了。
不過,目前這個 component 元件的 data 資料還有點問題:
data () {
return {
sent_id: sent_id,
orig_htmls: orig_htmls,
orig_texts: orig_texts,
tran_htmls: tran_htmls,
tran_texts: tran_texts
}
},
從 component 的角度來看,
它根本不知道 sent_id、orig_htmls... 這些東西是什麼?
因此,我們需要一個途徑,從外部把資料送進來。
這個外部途徑就是 props。
我們可以把原本 options 選項物件裡的 data 修改成相應的 props 如下:
props: [
'sent_id',
'orig_htmls',
'orig_texts',
'tran_htmls',
'tran_texts'
],
這幾個來自外部的 props 屬性,就像是函式所宣告的參數。
這樣一來,其餘的部分全都無需變動,
就完成我們的第一個 component 元件了。
有了這個 component 元件,
就可以再回到 panels.js 的 showInSentPanel() 裡,
把建立 Vue 實體的 options 選項物件簡化一番,
其中只會剩下 data 與 template 兩個部分:
vm = new Vue({
data () {
return {
sent_id: data.node,
orig_htmls: data.orig_htmls,
orig_texts: data.orig_texts,
tran_htmls: data.tran_htmls,
tran_texts: data.tran_texts,
}
},
template: `
<div id="app">
<orig_sent
:sent_id="sent_id"
:orig_htmls="orig_htmls"
:orig_texts="orig_texts"
:tran_htmls="tran_htmls"
:tran_texts="tran_texts"
></orig_sent>
</div>`
})
這樣是不是簡潔許多呢?
要提醒一下的是,雖然上面所列出的是 template,
但實際上我們是用 render(h) 來達到渲染的效果。
render(h) {
return h('div', {
attrs: { id: "app" }
}, [
h('orig_sent',{
props: {
sent_id: this.sent_id,
orig_htmls: this.orig_htmls,
orig_texts: this.orig_texts,
tran_htmls: this.tran_htmls,
tran_texts: this.tran_texts,
}
})
])
}
這其中的原因,之前就曾說明過,
主要是因為 Chrome Extension 不能接受行內腳本之故。
不過,用 template 來做為說明,還是比 render() 函式容易理解,
所以這裡還是用 template 來協助說明。
建議各位也可以同步維護 template 的內容,與 render() 一起同步更新,
實際上在編寫程式碼時,
可以先用 template 來撰寫想要的 html,
這樣會比直接用 render() 直觀許多。
而且這樣一來,template 也可做為 render 的註解說明,
對於閱讀程式碼的人來說,應該也很有幫助。我私心希望 Vue 官方可以重視這個問題,
進一步提供解決方案,
讓 template 的做法還能夠繼續沿用,
而不必被迫改用 render(),
否則 Vue 的好處就大打折扣了。
接下來讓我們再進一步,
把句子中的每個單詞,也拆出來變成一個一個的元件吧。
首先建立一個叫做 orig_token 新元件:
// 原文 token
Vue.component('orig_token', {...});
這個元件只需要從外部取得一個外部屬性:
props: [
"token"
],
這樣就可以建立 template 了:
template: `
<span style="margin: '2px';"
v-on:mouseover="activate"
v-on:mouseout="deactivate"
v-on:click="tokenClicked"
v-on:dblclick="dict_search"
>{{token}}</span>`
由於各種事件全都是針對 token,
所以我們直接把 orig_sent 裡的 methods 全都搬到這個元件中即可。
methods: {
activate: function (e) {...},
deactivate: function (e) {...},
tokenClicked: function (e) {...},
dict_search: function (e) {...},
},
之前 orig_sent 元件裡的 methods 可以全部移除,
並把 template 簡化成下面這樣:
template: `
<div id="orig_sent" style="margin: '20px';">
<orig_token v-for="token in tokens" :token="token">
</orig_token>
</div>`
如此一來,orig_sent 就只留下與句子相關的資料與樣板,
而所有與 token 詞語相關的資料與樣板,
全都打包在 orig_token 這個 component 元件中了
經過如此一番的整理之後,整個架構清楚許多,
將來進行更多修改調整時,一定能感受到更多的好處。
今天就先談到這裡囉 ^_^